﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Management;
using System.Diagnostics;

namespace System.Peppers.Wmi
{
    /// <summary>
    /// The Win32_Process WMI class represents a process on an operating system.  Quite a few more options are available over System.Diagnostics.Process, including starting a process on a remote system!
    /// </summary>
    public sealed class Process : CimSetting
    {
        #region Static
        private static class WmiProperties
        {
            public const string CommandLine = "CommandLine",
                CreationDate = "CreationDate",
                ExecutablePath = "ExecutablePath",
                Handle = "Handle",
                HandleCount = "HandleCount",
                KernelModeTime = "KernelModeTime",
                MaximumWorkingSetSize = "MaximumWorkingSetSize",
                MinimumWorkingSetSize = "MinimumWorkingSetSize",
                Name = "Name",
                OSName = "OSName",
                OtherOperationCount = "OtherOperationCount",
                OtherTransferCount = "OtherTransferCount",
                PageFaults = "PageFaults",
                PageFileUsage = "PageFileUsage",
                ParentProcessId = "ParentProcessId",
                PeakPageFileUsage = "PeakPageFileUsage",
                PeakVirtualSize = "PeakVirtualSize",
                PeakWorkingSetSize = "PeakWorkingSetSize",
                Priority = "Priority",
                PrivatePageCount = "PrivatePageCount",
                ProcessId = "ProcessId",
                ReadOperationCount = "ReadOperationCount",
                ReadTransferCount = "ReadTransferCount",
                SessionId = "SessionId",
                TerminationDate = "TerminationDate",
                ThreadCount = "ThreadCount",
                UserModeTime = "UserModeTime",
                VirtualSize = "VirtualSize",
                WindowsVersion = "WindowsVersion",
                WorkingSetSize = "WorkingSetSize",
                WriteOperationCount = "WriteOperationCount",
                WriteTransferCount = "WriteTransferCount";
        }

        private static class WmiMethods
        {
            public const string AttachDebugger = "AttachDebugger",
                Create = "Create",
                GetOwner = "GetOwner",
                GetOwnerSid = "GetOwnerSid",
                SetPriority = "SetPriority",
                Terminate = "Terminate";
        }

        private static class WmiParameters
        {
            public const string CommandLine = "CommandLine",
                CurrentDirectory = "CurrentDirectory",
                ProcessStartupInformation = "ProcessStartupInformation",
                ProcessId = "ProcessId",
                User = "User",
                Domain = "Domain",
                Sid = "Sid",
                Priority = "Priority",
                Reason = "Reason";
        }

        /// <summary>
        /// Enumerates processes on the system.
        /// </summary>
        /// <returns>List of processes</returns>
        public static IEnumerable<Process> GetProcesses()
        {
            foreach (ManagementObject item in GetInstances(WmiClasses.Win32_Process))
            {
                yield return new Process(item);
            }
        }

        /// <summary>
        /// Enumerates processes on a remote system.
        /// </summary>
        /// <param name="remoteParams">Connection options for remote system</param>
        /// <returns>List of processes</returns>
        public static IEnumerable<Process> GetProcesses(WmiRemoteParameters remoteParams)
        {
            foreach (ManagementObject item in GetInstances(WmiClasses.Win32_Process, remoteParams))
            {
                yield return new Process(item);
            }
        }

        /// <summary>
        /// The Create WMI class method creates a new process. A fully-qualified path must be specified in cases where the program to be launched is not in the search path of Winmgmt.exe. If the newly created process attempts to interact with objects on the target system without the appropriate access privileges, it is terminated without notification to this method.  For security reasons the Win32_Process.Create method cannot be used to start an interactive process remotely (this means visible in UI).
        /// </summary>
        /// <param name="commandLine">Command line to execute. The system adds a null character to the command line, trimming the string if necessary, to indicate which file was actually used.</param>
        /// <param name="processId">Global process identifier that can be used to identify a process. The value is valid from the time the process is created until the time the process is terminated.</param>
        /// <returns>Result of method</returns>
        public static ProcessResult Create(string commandLine, out uint processId)
        {
            return Create(commandLine, null, null, out processId);
        }

        /// <summary>
        /// The Create WMI class method creates a new process. A fully-qualified path must be specified in cases where the program to be launched is not in the search path of Winmgmt.exe. If the newly created process attempts to interact with objects on the target system without the appropriate access privileges, it is terminated without notification to this method.  For security reasons the Win32_Process.Create method cannot be used to start an interactive process remotely (this means visible in UI).
        /// </summary>
        /// <param name="commandLine">Command line to execute. The system adds a null character to the command line, trimming the string if necessary, to indicate which file was actually used.</param>
        /// <param name="currentDirectory">Current drive and directory for the child process. The string requires that the current directory resolves to a known path. A user can specify an absolute path or a path relative to the current working directory. If this parameter is NULL, the new process will have the same path as the calling process. This option is provided primarily for shells that must start an application and specify the application's initial drive and working directory.</param>
        /// <param name="processId">Global process identifier that can be used to identify a process. The value is valid from the time the process is created until the time the process is terminated.</param>
        /// <returns>Result of method</returns>
        public static ProcessResult Create(string commandLine, string currentDirectory, out uint processId)
        {
            return Create(commandLine, currentDirectory, null, out processId);
        }

        /// <summary>
        /// The Create WMI class method creates a new process. A fully-qualified path must be specified in cases where the program to be launched is not in the search path of Winmgmt.exe. If the newly created process attempts to interact with objects on the target system without the appropriate access privileges, it is terminated without notification to this method.  For security reasons the Win32_Process.Create method cannot be used to start an interactive process remotely (this means visible in UI).
        /// </summary>
        /// <param name="commandLine">Command line to execute. The system adds a null character to the command line, trimming the string if necessary, to indicate which file was actually used.</param>
        /// <param name="currentDirectory">Current drive and directory for the child process. The string requires that the current directory resolves to a known path. A user can specify an absolute path or a path relative to the current working directory. If this parameter is NULL, the new process will have the same path as the calling process. This option is provided primarily for shells that must start an application and specify the application's initial drive and working directory.</param>
        /// <param name="processStartupInformation">The startup configuration of a Windows process.</param>
        /// <param name="processId">Global process identifier that can be used to identify a process. The value is valid from the time the process is created until the time the process is terminated.</param>
        /// <returns>Result of method</returns>
        public static ProcessResult Create(string commandLine, string currentDirectory, ProcessStartup processStartupInformation, out uint processId)
        {
            Process process = new Process(WmiWrapper.GetStaticInstance(WmiClasses.Win32_Process));
            using (ManagementBaseObject outParam = process.ExecuteMethod(WmiMethods.Create, delegate(ManagementBaseObject inParam)
            {
                inParam[WmiParameters.CommandLine] = commandLine;
                inParam[WmiParameters.CurrentDirectory] = currentDirectory;
                inParam[WmiParameters.ProcessStartupInformation] = processStartupInformation.WmiInstance;
            }))
            {
                processId = Convert.ToUInt32(outParam[WmiParameters.ProcessId] ?? 0);
                return (ProcessResult)Convert.ToUInt32(outParam[WmiClasses.ReturnValue] ?? 0);
            }
        }

        /// <summary>
        /// Creates a new process on a remote system. A fully-qualified path must be specified in cases where the program to be launched is not in the search path of Winmgmt.exe. If the newly created process attempts to interact with objects on the target system without the appropriate access privileges, it is terminated without notification to this method.  For security reasons the Win32_Process.Create method cannot be used to start an interactive process remotely (this means visible in UI).
        /// </summary>
        /// <param name="remoteParams">Connection options for remote system</param>
        /// <param name="commandLine">Command line to execute. The system adds a null character to the command line, trimming the string if necessary, to indicate which file was actually used.</param>
        /// <param name="processId">Global process identifier that can be used to identify a process. The value is valid from the time the process is created until the time the process is terminated.</param>
        /// <returns>Result of method</returns>
        public static ProcessResult Create(WmiRemoteParameters remoteParams, string commandLine, out uint processId)
        {
            return Create(remoteParams, commandLine, null, null, out processId);
        }

        /// <summary>
        /// Creates a new process on a remote system. A fully-qualified path must be specified in cases where the program to be launched is not in the search path of Winmgmt.exe. If the newly created process attempts to interact with objects on the target system without the appropriate access privileges, it is terminated without notification to this method.  For security reasons the Win32_Process.Create method cannot be used to start an interactive process remotely (this means visible in UI).
        /// </summary>
        /// <param name="remoteParams">Connection options for remote system</param>
        /// <param name="commandLine">Command line to execute. The system adds a null character to the command line, trimming the string if necessary, to indicate which file was actually used.</param>
        /// <param name="currentDirectory">Current drive and directory for the child process. The string requires that the current directory resolves to a known path. A user can specify an absolute path or a path relative to the current working directory. If this parameter is NULL, the new process will have the same path as the calling process. This option is provided primarily for shells that must start an application and specify the application's initial drive and working directory.</param>
        /// <param name="processId">Global process identifier that can be used to identify a process. The value is valid from the time the process is created until the time the process is terminated.</param>
        /// <returns>Result of method</returns>
        public static ProcessResult Create(WmiRemoteParameters remoteParams, string commandLine, string currentDirectory, out uint processId)
        {
            return Create(remoteParams, commandLine, currentDirectory, null, out processId);
        }

        /// <summary>
        /// Creates a new process on a remote system. A fully-qualified path must be specified in cases where the program to be launched is not in the search path of Winmgmt.exe. If the newly created process attempts to interact with objects on the target system without the appropriate access privileges, it is terminated without notification to this method.  For security reasons the Win32_Process.Create method cannot be used to start an interactive process remotely (this means visible in UI).
        /// </summary>
        /// <param name="remoteParams">Connection options for remote system</param>
        /// <param name="commandLine">Command line to execute. The system adds a null character to the command line, trimming the string if necessary, to indicate which file was actually used.</param>
        /// <param name="currentDirectory">Current drive and directory for the child process. The string requires that the current directory resolves to a known path. A user can specify an absolute path or a path relative to the current working directory. If this parameter is NULL, the new process will have the same path as the calling process. This option is provided primarily for shells that must start an application and specify the application's initial drive and working directory.</param>
        /// <param name="processStartupInformation">The startup configuration of a Windows process.</param>
        /// <param name="processId">Global process identifier that can be used to identify a process. The value is valid from the time the process is created until the time the process is terminated.</param>
        /// <returns>Result of method</returns>
        public static ProcessResult Create(WmiRemoteParameters remoteParams, string commandLine, string currentDirectory, ProcessStartup processStartupInformation, out uint processId)
        {
            using (Process process = new Process(WmiWrapper.GetStaticInstance(WmiClasses.Win32_Process, remoteParams)))
            {
                using (ManagementBaseObject outParam = process.ExecuteMethod(WmiMethods.Create, delegate(ManagementBaseObject inParam)
                {
                    inParam[WmiParameters.CommandLine] = commandLine;
                    inParam[WmiParameters.CurrentDirectory] = currentDirectory;
                    inParam[WmiParameters.ProcessStartupInformation] = processStartupInformation.WmiInstance;
                }))
                {
                    processId = Convert.ToUInt32(outParam[WmiParameters.ProcessId] ?? 0);
                    return (ProcessResult)Convert.ToUInt32(outParam[WmiClasses.ReturnValue] ?? 0);
                }
            }
        }
        #endregion

        internal Process(ManagementObject wmiObject)
            : base(wmiObject)
        { }

        /// <summary>
        /// Command line used to start a specific process, if applicable. This property is new for Windows XP.
        /// </summary>
        public string CommandLine
        {
            get { return Convert.ToString(this[WmiProperties.CommandLine]); }
        }

        /// <summary>
        /// Date the process begins executing.
        /// </summary>
        public DateTime? CreationDate
        {
            get { return WmiUtil.ToDateTime(this[WmiProperties.CreationDate]); }
        }

        /// <summary>
        /// Path to the executable file of the process.  Example: C:\WINDOWS\EXPLORER.EXE
        /// </summary>
        public string ExecutablePath
        {
            get { return Convert.ToString(this[WmiProperties.ExecutablePath]); }
        }

        /// <summary>
        /// Process identifier.
        /// </summary>
        public string Handle
        {
            get { return Convert.ToString(this[WmiProperties.Handle]); }
        }

        /// <summary>
        /// Total number of open handles owned by the process. HandleCount is the sum of the handles currently open by each thread in this process. A handle is used to examine or modify the system resources. Each handle has an entry in a table that is maintained internally. Entries contain the addresses of the resources and data to identify the resource type.
        /// </summary>
        public uint HandleCount
        {
            get { return Convert.ToUInt32(this[WmiProperties.HandleCount]); }
        }

        /// <summary>
        /// Time in kernel mode, in 100 nanosecond units. If this information is not available, use a value of 0 (zero).
        /// </summary>
        public ulong KernelModeTime
        {
            get { return Convert.ToUInt64(this[WmiProperties.KernelModeTime]); }
        }

        /// <summary>
        /// Maximum working set size of the process. The working set of a process is the set of memory pages visible to the process in physical RAM. These pages are resident, and available for an application to use without triggering a page fault.
        /// </summary>
        public uint MaximumWorkingSetSize
        {
            get { return Convert.ToUInt32(this[WmiProperties.MaximumWorkingSetSize]); }
        }

        /// <summary>
        /// Minimum working set size of the process. The working set of a process is the set of memory pages visible to the process in physical RAM. These pages are resident and available for an application to use without triggering a page fault.
        /// </summary>
        public uint MinimumWorkingSetSize
        {
            get { return Convert.ToUInt32(this[WmiProperties.MinimumWorkingSetSize]); }
        }

        /// <summary>
        /// Name of the process including extension.
        /// </summary>
        public string Name
        {
            get { return Convert.ToString(this[WmiProperties.Name]); }
        }

        /// <summary>
        /// Name of the operating system.
        /// </summary>
        public string OSName
        {
            get { return Convert.ToString(this[WmiProperties.OSName]); }
        }

        /// <summary>
        /// Number of I/O operations performed that are not read or write operations.
        /// </summary>
        public ulong OtherOperationCount
        {
            get { return Convert.ToUInt64(this[WmiProperties.OtherOperationCount]); }
        }

        /// <summary>
        /// Amount of data transferred during operations that are not read or write operations.
        /// </summary>
        public ulong OtherTransferCount
        {
            get { return Convert.ToUInt64(this[WmiProperties.OtherTransferCount]); }
        }

        /// <summary>
        /// Number of page faults that a process generates.
        /// </summary>
        public uint PageFaults
        {
            get { return Convert.ToUInt32(this[WmiProperties.PageFaults]); }
        }

        /// <summary>
        /// Amount of page file space that a process is using currently. This value is consistent with the VMSize value in TaskMgr.exe.
        /// </summary>
        public uint PageFileUsage
        {
            get { return Convert.ToUInt32(this[WmiProperties.PageFileUsage]); }
        }

        /// <summary>
        /// Unique identifier of the process that creates a process. Process identifier numbers are reused, so they only identify a process for the lifetime of that process. It is possible that the process identified by ParentProcessId is terminated, so ParentProcessId may not refer to a running process. It is also possible that ParentProcessId incorrectly refers to a process that reuses a process identifier. You can use the CreationDate property to determine whether the specified parent was created after the process represented by this Win32_Process instance was created.
        /// </summary>
        public uint ParentProcessId
        {
            get { return Convert.ToUInt32(this[WmiProperties.ParentProcessId]); }
        }

        /// <summary>
        /// Maximum amount of page file space used during the life of a process.
        /// </summary>
        public uint PeakPageFileUsage
        {
            get { return Convert.ToUInt32(this[WmiProperties.PeakPageFileUsage]); }
        }

        /// <summary>
        /// Maximum virtual address space a process uses at any one time. Using virtual address space does not necessarily imply corresponding use of either disk or main memory pages. However, virtual space is finite, and by using too much the process might not be able to load libraries.
        /// </summary>
        public ulong PeakVirtualSize
        {
            get { return Convert.ToUInt64(this[WmiProperties.PeakVirtualSize]); }
        }

        /// <summary>
        /// Peak working set size of a process.
        /// </summary>
        public uint PeakWorkingSetSize
        {
            get { return Convert.ToUInt32(this[WmiProperties.PeakWorkingSetSize]); }
        }

        /// <summary>
        /// Scheduling priority of a process within an operating system. The higher the value, the higher priority a process receives. Priority values can range from 0 (zero), which is the lowest priority to 31, which is highest priority.
        /// </summary>
        public uint Priority
        {
            get { return Convert.ToUInt32(this[WmiProperties.Priority]); }
        }

        /// <summary>
        /// Current number of pages allocated that are only accessible to the process represented by this Win32_Process instance.
        /// </summary>
        public ulong PrivatePageCount
        {
            get { return Convert.ToUInt64(this[WmiProperties.PrivatePageCount]); }
        }

        /// <summary>
        /// Global process identifier that is used to identify a process. The value is valid from the time a process is created until it is terminated.
        /// </summary>
        public uint ProcessId
        {
            get { return Convert.ToUInt32(this[WmiProperties.ProcessId]); }
        }

        /// <summary>
        /// Number of read operations performed.
        /// </summary>
        public ulong ReadOperationCount
        {
            get { return Convert.ToUInt64(this[WmiProperties.ReadOperationCount]); }
        }

        /// <summary>
        /// Amount of data read.
        /// </summary>
        public ulong ReadTransferCount
        {
            get { return Convert.ToUInt64(this[WmiProperties.ReadTransferCount]); }
        }

        /// <summary>
        /// Unique identifier that an operating system generates when a session is created. A session spans a period of time from logon until logoff from a specific system.
        /// </summary>
        public uint SessionId
        {
            get { return Convert.ToUInt32(this[WmiProperties.SessionId]); }
        }

        /// <summary>
        /// Process was stopped or terminated. To get the termination time, a handle to the process must be held open. Otherwise, this property returns NULL.
        /// </summary>
        public DateTime? TerminationDate
        {
            get { return WmiUtil.ToDateTime(this[WmiProperties.TerminationDate]); }
        }

        /// <summary>
        /// Number of active threads in a process. An instruction is the basic unit of execution in a processor, and a thread is the object that executes an instruction. Each running process has at least one thread.
        /// </summary>
        public uint ThreadCount
        {
            get { return Convert.ToUInt32(this[WmiProperties.ThreadCount]); }
        }

        /// <summary>
        /// Time in user mode, in 100 nanosecond units. If this information is not available, use a value of 0 (zero).
        /// </summary>
        public ulong UserModeTime
        {
            get { return Convert.ToUInt64(this[WmiProperties.UserModeTime]); }
        }

        /// <summary>
        /// Current size of the virtual address space that a process is using, not the physical or virtual memory actually used by the process. Using virtual address space does not necessarily imply corresponding use of either disk or main memory pages. Virtual space is finite, and by using too much, the process might not be able to load libraries. This value is consistent with what you see in Perfmon.exe.
        /// </summary>
        public ulong VirtualSize
        {
            get { return Convert.ToUInt64(this[WmiProperties.VirtualSize]); }
        }

        /// <summary>
        /// Version of Windows in which the process is running.
        /// </summary>
        public string WindowsVersion
        {
            get { return Convert.ToString(this[WmiProperties.WindowsVersion]); }
        }

        /// <summary>
        /// Amount of memory in bytes that a process needs to execute efficiently—for an operating system that uses page-based memory management. If the system does not have enough memory (less than the working set size), thrashing occurs. If the size of the working set is not known, use NULL or 0 (zero). If working set data is provided, you can monitor the information to understand the changing memory requirements of a process.
        /// </summary>
        public ulong WorkingSetSize
        {
            get { return Convert.ToUInt64(this[WmiProperties.WorkingSetSize]); }
        }

        /// <summary>
        /// Number of write operations performed.
        /// </summary>
        public ulong WriteOperationCount
        {
            get { return Convert.ToUInt64(this[WmiProperties.WriteOperationCount]); }
        }

        /// <summary>
        /// Amount of data written.
        /// </summary>
        public ulong WriteTransferCount
        {
            get { return Convert.ToUInt64(this[WmiProperties.WriteTransferCount]); }
        }

        /// <summary>
        /// The AttachDebugger WMI class method starts the debugger that is currently registered for this process.
        /// </summary>
        /// <returns>Result of method</returns>
        public ProcessResult AttachDebugger()
        {
            return (ProcessResult)ExecuteMethodWithResult(WmiMethods.AttachDebugger, null);
        }

        /// <summary>
        /// The GetOwner WMI class method retrieves the user name and domain name under which the process is running.
        /// </summary>
        /// <param name="user">Returns the user name of the owner of this process.</param>
        /// <param name="domain">Returns the domain name under which this process is running.</param>
        /// <returns>Result of method</returns>
        public ProcessResult GetOwner(out string user, out string domain)
        {
            using (ManagementBaseObject outParam = ExecuteMethod(WmiMethods.GetOwner, null))
            {
                user = Convert.ToString(outParam[WmiParameters.User]);
                domain = Convert.ToString(outParam[WmiParameters.Domain]);
                return (ProcessResult)Convert.ToUInt32(outParam[WmiClasses.ReturnValue] ?? 0);
            }
        }

        /// <summary>
        /// The GetOwnerSid WMI class method retrieves the security identifier (SID) for the owner of this process.
        /// </summary>
        /// <param name="sid">Returns the security identifier descriptor for this process.</param>
        /// <returns>Result of method</returns>
        public ProcessResult GetOwnerSid(out string sid)
        {
            using (ManagementBaseObject outParam = ExecuteMethod(WmiMethods.GetOwnerSid, null))
            {
                sid = Convert.ToString(outParam[WmiParameters.Sid]);
                return (ProcessResult)Convert.ToUInt32(outParam[WmiClasses.ReturnValue] ?? 0);
            }
        }

        /// <summary>
        /// The SetPriority WMI class method attempts to change the execution priority of the process. To set the priority to Realtime, the caller must have SeIncreaseBasePriorityPrivilege (SE_INC_BASE_PRIORITY_PRIVILEGE). Without this privilege, the highest the priority can be set to is High Priority. 
        /// </summary>
        /// <param name="priority">New priority class for the process.</param>
        /// <returns>Result of method</returns>
        public ProcessResult SetPriority(ProcessPriorityClass priority)
        {
            return (ProcessResult)ExecuteMethodWithResult(WmiMethods.SetPriority, delegate(ManagementBaseObject inParam)
            {
                inParam[WmiParameters.Priority] = priority;
            });
        }

        /// <summary>
        /// The Terminate WMI class method terminates a process and all of its threads. 
        /// </summary>
        /// <param name="reason">Exit code for the process and for all of the threads terminated as a result of this call.</param>
        /// <returns>Result of method</returns>
        public ProcessResult Terminate(uint reason)
        {
            return (ProcessResult)ExecuteMethodWithResult(WmiMethods.Terminate, delegate(ManagementBaseObject inParam)
            {
                inParam[WmiParameters.Reason] = reason;
            });
        }
    }
}
